package com.gitee.feizns.bean;

import com.gitee.feizns.StringUtils;
import com.gitee.feizns.reflect.FieldUtils;
import com.gitee.feizns.reflect.MethodUtils;
import lombok.NonNull;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.stream.Stream;

/**
 * 属性操作
 * @author feizns
 * @since 2019/6/5
 */
public abstract class PropertyUtils {

    public static final String SET_PREFIX = "set";
    public static final String GET_PREFIX = "get";
    public static final String IS_PREFIX = "is";

    private static final List<String> prefix = Arrays.asList(GET_PREFIX, SET_PREFIX, IS_PREFIX);

    /**
     * 所有的属性
     * @param target
     * @return
     */
    public static final Stream<Property<Object>> props(Object target) {
        Method[] methods = target.getClass().getMethods();
        List<Property<Object>> properties = new ArrayList<>();
        for (Method method : methods) {
            if ( isSet(method) || isGet(method) ) {
                String propsName = getPropsName(method.getName());
                boolean anyMatch = properties.stream().anyMatch(prop -> propsName.equals(prop.name()));
                if ( anyMatch == false ) {
                    properties.add(Property.of(target, propsName));
                }
            }
        }
        return properties.stream();
    }

    /**
     * 所有可写的属性
     * @param target
     * @return
     */
    public static final Stream<Property<Object>> writableProps(Object target) {
        return props(target).filter(Property::isWritable);
    }

    /**
     * 可读的属性
     * @param target
     * @return
     */
    public static final Stream<Property<Object>> readableProps(Object target) {
        return props(target).filter(Property::isReadable);
    }

    /**
     * 可读写的属性
     * @param target
     * @return
     */
    public static final Stream<Property<Object>> readAndWriteProps(Object target) {
        return props(target).filter(prop -> prop.isReadable() && prop.isWritable());
    }

    /**
     * 是否写方法
     * @param method
     * @return
     */
    public static final boolean isSet(@NonNull Method method) {
        return method.getParameterCount() == 1 && method.getName().startsWith(SET_PREFIX);
    }

    /**
     * 是否是一个写方法
     * @param method
     * @return
     */
    public static final boolean isGet(@NonNull Method method) {
        return method.getParameterCount() == 0 && (method.getName().startsWith(GET_PREFIX) || method.getName().startsWith(IS_PREFIX));
    }

    /**
     * 获取属性名称
     * @return
     */
    public static final String getPropsName(String name) {
        for (String pre : prefix) {
            if ( name.startsWith(pre) ) {
                return StringUtils.firstLower(name.substring(pre.length()));
            }
        }
        return name;
    }

    /**
     * 写方法
     * @param type
     * @param name
     * @return
     */
    public static final Method getWriteMethod(@NonNull Class<?> type, @NonNull String name, Class<?> parameterType) {
        return getMayHavePrefixMethod(new String[]{ SET_PREFIX }, type, name, null, parameterType);
    }

    /**
     * 读方法
     * @param type
     * @param name
     * @return
     */
    public static final Method getReadMethod(@NonNull Class<?> type, @NonNull String name) {
        return getMayHavePrefixMethod(new String[]{ GET_PREFIX, IS_PREFIX }, type, name,
                (method, prefix) -> prefix != IS_PREFIX || (method.getReturnType() == Boolean.class || method.getReturnType() == boolean.class));
    }

    private static final Method getMayHavePrefixMethod(String[] prefix,
                                                       @NonNull Class<?> type,
                                                       @NonNull String name,
                                                       BiPredicate<Method, String> finalConfirm,
                                                       Class<?>... parameterType) {
        for (String pre : prefix) {
            if ( name.startsWith(pre) ) {
                return MethodUtils.get(type, name, parameterType);
            } else {
                Method method = MethodUtils.get(type, pre + StringUtils.firstUpper(name), parameterType);
                if ( finalConfirm != null ? (method != null && finalConfirm.test(method, pre)) : (method != null) ) {
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * 获取字段
     * @param type
     * @param name
     * @return
     */
    public static final Field getField(@NonNull Class<?> type, @NonNull String name) {
        return FieldUtils.get(type, getPropsName(name));
    }

}
